﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
using System.Reflection;
using WcfContrib.Extensions.Context;
using WcfContrib.Helpers;
using WcfContrib.Utils;

namespace WcfContrib.Extensions.Invokers
{
    class MockingInvoker : IOperationInvoker
    {
        internal const ActivationOption DefaultActivationOption = ActivationOption.UriMatch;
        const string UriMatch = "mock";

        object[] outputParams;
        IOperationInvoker innerOperationInvoker;
        
        public ActivationOption ActivationOption { get; set; }
        public object ReturnValue { get; set; }
        
        bool IsActive
        {
            get
            {
                ActivationContextExtension context = ActivationContextExtension.Current;

                return ActivationOption == ActivationOption.Enabled
                    || (ActivationOption == ActivationOption.UriMatch && context != null
                        && MessageHelper.UriContainsSegment(context.ToHeader, UriMatch));
            }
        }

        public MockingInvoker(MethodInfo method, IOperationInvoker innerOperationInvoker)
            : this(method, DefaultActivationOption, innerOperationInvoker)
        {

        }

        public MockingInvoker(MethodInfo method, ActivationOption activationOption, IOperationInvoker innerOperationInvoker)
        {
            ActivationOption = activationOption;
            this.innerOperationInvoker = innerOperationInvoker;
            LoadMethod(method);
        }

        [System.Diagnostics.DebuggerStepThrough]
        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            if (IsActive)
            {
                outputs = outputParams;
                return ReturnValue;
            }
            else
            {
                return innerOperationInvoker.Invoke(instance, inputs, out outputs);
            }
        }

        void LoadMethod(MethodInfo method)
        {
            ReturnValue = TypeHelper.CreateDefaultOfType(method.ReturnType);

            List<object> outParams = new List<object>();

            var pQuery = method.GetParameters().Where(p => p.ParameterType.IsByRef || p.IsOut);
            foreach (var item in pQuery)
            {
                outParams.Add(TypeHelper.CreateDefaultOfType(item.ParameterType));
            }

            outputParams = outParams.ToArray();
        }

        #region IOperationInvoker Members

        public object[] AllocateInputs()
        {
            return innerOperationInvoker.AllocateInputs();
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return innerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            return innerOperationInvoker.InvokeEnd(instance, out outputs, result);
        }

        public bool IsSynchronous
        {
            get { return innerOperationInvoker.IsSynchronous; }
        }

        #endregion
    }

    class MockingInvokerBehaviorAttribute : Attribute, IServiceBehavior, IOperationBehavior
    {
        public ActivationOption ActivationOption { get; set; }

        public MockingInvokerBehaviorAttribute()
            : this(MockingInvoker.DefaultActivationOption)
        {

        }

        public MockingInvokerBehaviorAttribute(ActivationOption activationOption)
        {
            ActivationOption = activationOption;
        }

        #region IServiceBehavior Members

        public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
            var contracts = serviceDescription.Endpoints.Select(e => e.Contract).Distinct();

            foreach (var contract in contracts)
            {
                foreach (var operation in contract.Operations)
                {
                    operation.Behaviors.Add(this);
                }
            }
        }

        public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
        }

        #endregion

        #region IOperationBehavior Members

        public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {
        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            dispatchOperation.Invoker = new MockingInvoker(operationDescription.SyncMethod,
                ActivationOption, dispatchOperation.Invoker);
        }

        public void Validate(OperationDescription operationDescription)
        {
        }

        #endregion
    }
}
